package vn.vfriends.vcms.admin.service.impl;

import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import javax.annotation.Resource;
import javax.inject.Inject;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.EntityNotFoundException;
import javax.persistence.PersistenceUnit;
import javax.persistence.Query;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Expression;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import javax.transaction.Status;
import javax.transaction.UserTransaction;
import javax.validation.ConstraintViolation;
import javax.validation.Validator;
import vn.vfriends.vcms.admin.model.Category;
import vn.vfriends.vcms.admin.model.HotCategory;
import vn.vfriends.vcms.admin.model.Item;
import vn.vfriends.vcms.admin.service.CategoryService;
import vn.vfriends.vcms.admin.service.impl.exceptions.IllegalOrphanException;
import vn.vfriends.vcms.admin.service.impl.exceptions.NonexistentEntityException;
import vn.vfriends.vcms.admin.service.impl.exceptions.RollbackFailureException;
import vn.vfriends.vcms.admin.service.impl.exceptions.ViolationException;

/**
 *
 * @author tuan@vfriends.vn
 */
public class CategoryJpaController implements CategoryService {

    @Resource
    private UserTransaction utx = null;
    @PersistenceUnit(unitName = "vcmsPU")
    private EntityManagerFactory emf = null;
    @Inject
    private Validator validator;

    public EntityManager getEntityManager() {
        return emf.createEntityManager();
    }

    @Override
    public void createCategory(Category category) throws IllegalOrphanException, RollbackFailureException, Exception {
        if (category.getChildren() == null) {
            category.setChildren(new ArrayList<Category>());
        }
        if (category.getItems() == null) {
            category.setItems(new ArrayList<Item>());
        }
        
        EntityManager em = null;
        try {
            Set<ConstraintViolation<Category>> violations = this.validator.validate(category);
            if (violations.size() > 0) {
                ViolationException exception = new ViolationException(violations);
                throw exception;
            }
            
            utx.begin();
            em = getEntityManager();
            //  Verify parent
            Category parent = category.getParent();
            if (parent != null) {
                parent = em.getReference(parent.getClass(), parent.getId());
                category.setParent(parent);
            }
            //  Verify children
            List<Category> attachedChildren = new ArrayList<Category>();
            for (Category childToAttach : category.getChildren()) {
                childToAttach = em.getReference(childToAttach.getClass(), childToAttach.getId());
                attachedChildren.add(childToAttach);
            }
            category.setChildren(attachedChildren);
            //  Verify items
            List<Item> attachedItems = new ArrayList<Item>();
            for (Item itemToAttach : category.getItems()) {
                itemToAttach = em.getReference(itemToAttach.getClass(), itemToAttach.getId());
                attachedItems.add(itemToAttach);
            }
            category.setItems(attachedItems);
            
            //  Persist category
            em.persist(category);
            
            //  Update parent
            if (parent != null) {
                parent.getChildren().add(category);
                parent = em.merge(parent);
            }
            //  Update children
            for (Category child : category.getChildren()) {
                Category oldParentOfChild = child.getParent();
                child.setParent(category);
                child = em.merge(child);
                if (oldParentOfChild != null) {
                    oldParentOfChild.getChildren().remove(child);
                    oldParentOfChild = em.merge(oldParentOfChild);
                }
            }
            //  Update items
            for (Item item : category.getItems()) {
                Category oldCategoryOfItem = item.getCategory();
                item.setCategory(category);
                item = em.merge(item);
                if (oldCategoryOfItem != null) {
                    oldCategoryOfItem.getItems().remove(item);
                    oldCategoryOfItem = em.merge(oldCategoryOfItem);
                }
            }
            utx.commit();
        } catch (ViolationException ve) {
            throw ve;
        } catch (Exception ex) {
            try {
                if (utx.getStatus() == Status.STATUS_ACTIVE) {
                    utx.rollback();
                }
            } catch (Exception re) {
                throw new RollbackFailureException("An error occurred attempting to roll back the transaction.", re);
            }
            throw ex;
        } finally {
            if (em != null) {
                em.close();
            }
        }
    }

    @Override
    public void editCategory(Category category) throws IllegalOrphanException, NonexistentEntityException, RollbackFailureException, Exception {
        EntityManager em = null;
        try {
            Set<ConstraintViolation<Category>> violations = this.validator.validate(category);
            if (violations.size() > 0) {
                ViolationException exception = new ViolationException(violations);
                throw exception;
            }
            
            utx.begin();
            em = getEntityManager();
            Category persistentCategory = em.find(Category.class, category.getId());
            
            Category oldParent = persistentCategory.getParent();
            Category newParent = category.getParent();
            
            List<Category> oldChildren = persistentCategory.getChildren();
            List<Category> newChildren = category.getChildren();
            
            List<Item> oldItems = persistentCategory.getItems();
            List<Item> newItems = category.getItems();
            
            List<String> illegalOrphanMessages = null;
            
            for (Category oldChild : oldChildren) {
                if (!newChildren.contains(oldChild)) {
                    if (illegalOrphanMessages == null) {
                        illegalOrphanMessages = new ArrayList<String>();
                    }
                    illegalOrphanMessages.add("You must retain Category " + oldChild + " since its parent field is not nullable.");
                }
            }
            for (Item oldItem : oldItems) {
                if (!newItems.contains(oldItem)) {
                    if (illegalOrphanMessages == null) {
                        illegalOrphanMessages = new ArrayList<String>();
                    }
                    illegalOrphanMessages.add("You must retain Item " + oldItem + " since its category field is not nullable.");
                }
            }
            if (illegalOrphanMessages != null) {
                throw new IllegalOrphanException(illegalOrphanMessages);
            }
            //  Verify new parent
            if (newParent != null) {
                newParent = em.getReference(newParent.getClass(), newParent.getId());
                category.setParent(newParent);
            }
            //  Verify new children
            List<Category> attachedNewChildren = new ArrayList<Category>();
            for (Category newChildToAttach : newChildren) {
                newChildToAttach = em.getReference(newChildToAttach.getClass(), newChildToAttach.getId());
                attachedNewChildren.add(newChildToAttach);
            }
            newChildren = attachedNewChildren;
            category.setChildren(newChildren);
            
            //  Verify new items
            List<Item> attachedNewItems = new ArrayList<Item>();
            for (Item newItemToAttach : newItems) {
                newItemToAttach = em.getReference(newItemToAttach.getClass(), newItemToAttach.getId());
                attachedNewItems.add(newItemToAttach);
            }
            newItems = attachedNewItems;
            category.setItems(newItems);
            
            //  Merge category
            category = em.merge(category);
            
            //  Update new parent and old parent
            if (oldParent != null && !oldParent.equals(newParent)) {
                oldParent.getChildren().remove(category);
                List<HotCategory> hotCategories = oldParent.getHotCategories();
                for(HotCategory hotCategory : hotCategories) {
                    if (hotCategory.getCategory().equals(category)) {
                        hotCategories.remove(hotCategory);
                        break;
                    }
                }
                oldParent = em.merge(oldParent);
            }
            if (newParent != null && !newParent.equals(oldParent)) {
                newParent.getChildren().add(category);
                newParent = em.merge(newParent);
            }
            
            //  Check new children
            for (Category newChild : newChildren) {
                if (!oldChildren.contains(newChild)) {
                    Category oldParentOfNewChild = newChild.getParent();
                    newChild.setParent(category);
                    newChild = em.merge(newChild);
                    if (oldParentOfNewChild != null && !oldParentOfNewChild.equals(category)) {
                        oldParentOfNewChild.getChildren().remove(newChild);
                        oldParentOfNewChild = em.merge(oldParentOfNewChild);
                    }
                }
            }
            //  Check new items
            for (Item newItem : newItems) {
                if (!oldItems.contains(newItem)) {
                    Category oldCategoryOfNewItem = newItem.getCategory();
                    newItem.setCategory(category);
                    newItem = em.merge(newItem);
                    if (oldCategoryOfNewItem != null && !oldCategoryOfNewItem.equals(category)) {
                        oldCategoryOfNewItem.getItems().remove(newItem);
                        oldCategoryOfNewItem = em.merge(oldCategoryOfNewItem);
                    }
                }
            }
            utx.commit();
        } catch (ViolationException ve) {
            throw ve;
        } catch (Exception ex) {
            try {
                if (utx.getStatus() == Status.STATUS_ACTIVE) {
                    utx.rollback();
                }
            } catch (Exception re) {
                throw new RollbackFailureException("An error occurred attempting to roll back the transaction.", re);
            }
            String msg = ex.getLocalizedMessage();
            if (msg == null || msg.length() == 0) {
                Long id = category.getId();
                if (findCategory(id) == null) {
                    throw new NonexistentEntityException("The category with id " + id + " no longer exists.");
                }
            }
            throw ex;
        } finally {
            if (em != null) {
                em.close();
            }
        }
    }

    @Override
    public void destroyCategory(Long id) throws IllegalOrphanException, NonexistentEntityException, RollbackFailureException, Exception {
        EntityManager em = null;
        try {
            utx.begin();
            em = getEntityManager();
            Category category;
            try {
                category = em.getReference(Category.class, id);
                category.getId();
            } catch (EntityNotFoundException enfe) {
                throw new NonexistentEntityException("The category with id " + id + " no longer exists.", enfe);
            }
            List<String> illegalOrphanMessages = null;
            List<Category> childrenOrphanCheck = category.getChildren();
            for (Category childOrphanCheck : childrenOrphanCheck) {
                if (illegalOrphanMessages == null) {
                    illegalOrphanMessages = new ArrayList<String>();
                }
                illegalOrphanMessages.add("This Category (" + category + ") cannot be destroyed since the Category " + childOrphanCheck + " in its children field has a non-nullable parent field.");
            }
            List<Item> itemsOrphanCheck = category.getItems();
            for (Item itemOrphanCheck : itemsOrphanCheck) {
                if (illegalOrphanMessages == null) {
                    illegalOrphanMessages = new ArrayList<String>();
                }
                illegalOrphanMessages.add("This Category (" + category + ") cannot be destroyed since the Item " + itemOrphanCheck + " in its items field has a non-nullable category field.");
            }
            if (illegalOrphanMessages != null) {
                throw new IllegalOrphanException(illegalOrphanMessages);
            }
            //  Remove in children of parent
            Category parent = category.getParent();
            if (parent != null) {
                parent.getChildren().remove(category);
                List<HotCategory> hotCategories = parent.getHotCategories();
                for(HotCategory hotCategory : hotCategories) {
                    if (hotCategory.getCategory().equals(category)) {
                        hotCategories.remove(hotCategory);
                        break;
                    }
                }
                parent = em.merge(parent);
            }
            
            //  Remove category
            em.remove(category);
            utx.commit();
        } catch (Exception ex) {
            try {
                if (utx.getStatus() == Status.STATUS_ACTIVE) {
                    utx.rollback();
                }
            } catch (Exception re) {
                throw new RollbackFailureException("An error occurred attempting to roll back the transaction.", re);
            }
            throw ex;
        } finally {
            if (em != null) {
                em.close();
            }
        }
    }

    @Override
    public List<Category> findCategories() {
        return findCategories(true, -1, -1);
    }
    
    @Override
    public List<Category> findParentCategories() {
        EntityManager em = getEntityManager();
        try {
            CriteriaBuilder cb = em.getCriteriaBuilder();
            CriteriaQuery<Category> criteria = cb.createQuery(Category.class);
            Root<Category> entity = criteria.from(Category.class);
            
            Predicate predicate = cb.conjunction();
            predicate = cb.and(predicate, cb.isNotEmpty((Expression)entity.get("children")));
            
            criteria.where(predicate);
            Query query = em.createQuery(criteria);
            return query.getResultList();
        } finally {
            em.close();
        }
    }

    @Override
    public List<Category> findOrderedCategories(Category parent) {
        EntityManager em = getEntityManager();
        try {
            CriteriaBuilder cb = em.getCriteriaBuilder();
            CriteriaQuery<Category> criteria = cb.createQuery(Category.class);
            Root<Category> entity = criteria.from(Category.class);
            
            Predicate predicate = cb.conjunction();
            if(parent != null) {
                predicate = cb.and(predicate, cb.equal(entity.get("parent"), parent));
            } else {
                predicate = cb.and(predicate, cb.isNull(entity.get("parent")));
            }
            
            criteria.where(predicate);
            criteria.orderBy(cb.asc(entity.get("orderNumber")));
            Query query = em.createQuery(criteria);
            return query.getResultList();
        } finally {
            em.close();
        }
    }
    
    @Override
    public List<Category> findCategories(int maxResults, int firstResult) {
        return findCategories(false, maxResults, firstResult);
    }

    private List<Category> findCategories(boolean all, int maxResults, int firstResult) {
        EntityManager em = getEntityManager();
        try {
            CriteriaQuery cq = em.getCriteriaBuilder().createQuery();
            cq.select(cq.from(Category.class));
            Query q = em.createQuery(cq);
            if (!all) {
                q.setMaxResults(maxResults);
                q.setFirstResult(firstResult);
            }
            return q.getResultList();
        } finally {
            em.close();
        }
    }

    @Override
    public Category findCategory(Long id) {
        EntityManager em = getEntityManager();
        try {
            return em.find(Category.class, id);
        } finally {
            em.close();
        }
    }

    @Override
    public Category findCategoryByName(String name) {
        EntityManager em = getEntityManager();
        try {
            Query query = em.createNamedQuery("Category.findByName", Category.class);
            query.setParameter("name", name);
            List<Category> categoryList = query.getResultList();
            if (categoryList != null && !categoryList.isEmpty()) {
                return categoryList.get(0);
            } else {
                return null;
            }
        } finally {
            em.close();
        }
    }
    
    @Override
    public int getCategoryCount() {
        EntityManager em = getEntityManager();
        try {
            CriteriaQuery cq = em.getCriteriaBuilder().createQuery();
            Root<Category> rt = cq.from(Category.class);
            cq.select(em.getCriteriaBuilder().count(rt));
            Query q = em.createQuery(cq);
            return ((Long) q.getSingleResult()).intValue();
        } finally {
            em.close();
        }
    }
    
}
